package repository

import (
	"context"
	"fmt"
	"gluenet/modules/manager/server/adapter/repository/etcd"
	"gluenet/modules/manager/server/domain/dependency"
	"gluenet/modules/manager/server/domain/event"
	"gluenet/modules/manager/server/infra"
	"gluenet/modules/manager/server/infra/prepare"
	etcdClient "go.etcd.io/etcd/client/v3"
	"gopkg.in/yaml.v3"
)

func init() {
	prepare.RegisterRepo(func() {
		repo := NewDetailRepo()
		prepare.InjectDependency(func() dependency.DetailRepo {
			return repo
		})
	})
}

type Detail struct {
}

func (i *Detail) PutDetailKV(repoKey string, repoValue interface{}) error {
	bts, err := yaml.Marshal(repoValue)
	if err != nil {
		return err
	}
	_, err = etcd.DB.Put(context.Background(), repoKey, string(bts))
	return err
}

func (i *Detail) DelDetailKV(repoKey string) error {
	_, err := etcd.DB.Delete(context.Background(), repoKey)
	return err
}

func (i *Detail) GetByRepoKey(key string) (interface{}, error) {
	if key == "" {
		return nil, fmt.Errorf("key is null")
	}
	res, err := etcd.DB.Get(context.Background(), key)
	if err != nil {
		return nil, err
	}
	if len(res.Kvs) > 1 {
		return nil, fmt.Errorf("much key in etcd")
	}
	if len(res.Kvs) == 0 {
		return nil, infra.DataNotFind
	}
	var value interface{}
	if err = yaml.Unmarshal(res.Kvs[0].Value, &value); err != nil {
		return nil, err
	}
	return value, nil
}

func (i *Detail) GetRepoKeyOnlyWithPrefix(key string) ([]string, error) {
	if key == "" {
		return nil, fmt.Errorf("key is null")
	}
	res, err := etcd.DB.Get(context.Background(), key, etcdClient.WithPrefix(), etcdClient.WithKeysOnly())
	if err != nil {
		return nil, err
	}
	if len(res.Kvs) == 0 {
		return nil, infra.DataNotFind
	}
	var value = make([]string, len(res.Kvs))
	for k, v := range res.Kvs {
		value[k] = string(v.Key)
	}
	return value, nil
}

func (i *Detail) DeleteKeyWithPrefix(key string) error {
	if key == "" {
		return fmt.Errorf("key is null")
	}
	_, err := etcd.DB.Delete(context.Background(), key, etcdClient.WithPrefix())
	if err != nil {
		return err
	}
	return nil
}

func (i *Detail) Watch(repoKey string) (<-chan *event.RepoWatch, context.CancelFunc) {
	ctx := context.Background()
	ctxNew, cancel := context.WithCancel(ctx)
	ch := etcd.DB.Watch(ctxNew, repoKey, etcdClient.WithPrefix())

	rch := make(chan *event.RepoWatch)
	go func() {
		for {
			select {
			case <-ctxNew.Done():
				close(rch)
				return
			case data := <-ch:
				for _, e := range data.Events {
					wh := &event.RepoWatch{
						RepoKey:   string(e.Kv.Key),
						RepoValue: e.Kv.Value,
					}
					switch e.Type {
					case etcdClient.EventTypeDelete:
						wh.EventType = event.DELETE
					case etcdClient.EventTypePut:
						wh.EventType = event.PUT
					}
					rch <- wh
				}
			}
		}
	}()
	return rch, cancel
}

func NewDetailRepo() *Detail {
	r := &Detail{}
	return r
}
